//################################################################################
// MIT License
// Copyright (c) 2024 ZhangYihua
//
// Change Logs:
// Date           Author       Notes
// 2020-05-02     ZhangYihua   first version
//
// Description  : arbiter with weighted round robin algorithm
//################################################################################

module arb_wrr #(
parameter           PORT_NUM                = 8,
parameter           WEIGHT_BW               = 4,        // bit width of single port weight 

// the following parameters are calculated automatically
parameter           ALL_WEIGHT_BW           = PORT_NUM*WEIGHT_BW
) ( 
input                                       rst_n,
input                                       clk,

input               [PORT_NUM-1:0]          req,        // raw request vector
output                                      req_exist,  // logic OR of all reqs whose cfg_weight>0

input                                       sch_en,     // never set sch_en to 1 unless req_exist==1 
output              [PORT_NUM-1:0]          gnt,        // immediate grants according to req and cnt_weight
output              [PORT_NUM-1:0]          gnt_hld,    // locked grants at sch_en==1

input               [ALL_WEIGHT_BW-1:0]     cfg_weight  // range of single port is [0 : 2^WEIGHT_BW-1]
);

//################################################################################
// define local varialbe and localparam
//################################################################################
wire                [PORT_NUM-1:0]          req_cfg_mask;
wire                [PORT_NUM-1:0]          req_cnt_mask;
wire                                        req_cnt_mask_or;
wire                [PORT_NUM-1:0]          req_sp0;
wire                [PORT_NUM-1:0]          req_sp1;
wire                [PORT_NUM-1:0]          req_sp2;
wire                                        req_exist_sp0;
wire                                        req_exist_sp1;
wire                                        req_exist_sp2;
wire                [PORT_NUM-1:0]          gnt_sp0;
wire                [PORT_NUM-1:0]          gnt_sp1;
wire                [PORT_NUM-1:0]          gnt_sp2;
wire                [PORT_NUM-1-1:0]        gnt_pos_sp0;
wire                [PORT_NUM-1-1:0]        gnt_pos_sp1;
wire                [PORT_NUM-1-1:0]        gnt_pos_sp2;
wire                [PORT_NUM-1-1:0]        gnt_pos_c;
reg                 [PORT_NUM-1-1:0]        gnt_pos;
reg                                         sch_en_1d;
wire                [PORT_NUM-1:0]          gnt_1d;
reg                 [WEIGHT_BW-1:0]         cnt_weight_dec[PORT_NUM-1:0];
reg                 [WEIGHT_BW-1:0]         cnt_weight    [PORT_NUM-1:0];
wire                [PORT_NUM-1:0]          cnt_weight_vld;
wire                [PORT_NUM-1:0]          cfg_weight_vld;

//################################################################################
// main
//################################################################################

assign req_cnt_mask = req & cnt_weight_vld;     // mask req with current weight
assign req_cfg_mask = req & cfg_weight_vld;     // mask req with cfg weight

assign req_sp0 = req_cnt_mask & {gnt_pos, 1'b0};
assign req_sp1 = req_cnt_mask;
assign req_sp2 = req_cfg_mask;

arb_sp #(.PORT_NUM(PORT_NUM)) u_sp0 (.req(req_sp0), .req_exist(req_exist_sp0), .gnt_pos(gnt_pos_sp0), .gnt(gnt_sp0));
arb_sp #(.PORT_NUM(PORT_NUM)) u_sp1 (.req(req_sp1), .req_exist(req_exist_sp1), .gnt_pos(gnt_pos_sp1), .gnt(gnt_sp1));
arb_sp #(.PORT_NUM(PORT_NUM)) u_sp2 (.req(req_sp2), .req_exist(req_exist_sp2), .gnt_pos(gnt_pos_sp2), .gnt(gnt_sp2));

assign req_exist = req_exist_sp2;

assign gnt       = (req_exist_sp0==1'b1) ? gnt_sp0 :
                   (req_exist_sp1==1'b1) ? gnt_sp1 :
                                           gnt_sp2;

assign gnt_pos_c = (req_exist_sp0==1'b1) ? gnt_pos_sp0 :
                   (req_exist_sp1==1'b1) ? gnt_pos_sp1 :
                                           gnt_pos_sp2;

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        gnt_pos <=`U_DLY {PORT_NUM-1{1'b0}};
    end else begin
        if (sch_en==1'b1)
            gnt_pos <=`U_DLY gnt_pos_c;
        else
            ;
    end
end

assign gnt_hld = {1'b1, gnt_pos} ^ {gnt_pos, 1'b0};
always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        sch_en_1d <=`U_DLY 1'b0;
    end else begin
        sch_en_1d <=`U_DLY sch_en;
    end
end

assign req_cnt_mask_or = |req_cnt_mask;
    
genvar g0;
generate for (g0=0; g0<PORT_NUM; g0=g0+1) begin:G_PORT

    assign gnt_1d[g0] = sch_en_1d & gnt_hld[g0];

    always@(posedge clk or negedge rst_n) begin
        if (rst_n==1'b0) begin
            cnt_weight[g0] <=`U_DLY {WEIGHT_BW{1'b0}};
        end else begin
            if ((sch_en==1'b1) && (req_cnt_mask_or==1'b0))
                cnt_weight[g0] <=`U_DLY cfg_weight[g0*WEIGHT_BW+:WEIGHT_BW];
            else if (gnt_1d[g0]==1'b1)
                cnt_weight[g0] <=`U_DLY cnt_weight[g0] - 1'b1;
            else
                ;
        end
    end

    always@(*) begin
        cnt_weight_dec[g0] = {WEIGHT_BW{1'b0}};

        cnt_weight_dec[g0][0] = gnt_1d[g0];
    end

    assign cnt_weight_vld[g0] = (cnt_weight[g0]>cnt_weight_dec[g0]) ? 1'b1 : 1'b0;

    assign cfg_weight_vld[g0] = |cfg_weight[g0*WEIGHT_BW+:WEIGHT_BW];
    
end endgenerate

//################################################################################
// ASSERTION
//################################################################################

`ifdef CBB_ASSERT_ON
// synopsys translate_off

reg     SCH_EN_CHKEN;

a_sch_when_exist: assert property (@(posedge clk) disable iff (!rst_n)
    ((SCH_EN_CHKEN!==1'b0)&sch_en |-> req_exist)
) else begin
    $error("input port 'sch_en' can't be set to 1 when 'req_exist' is 0");
end

// generally define CBB_DEBUG_ON only when debugging CBB
`ifdef CBB_DEBUG_ON

a_gnt_null: assert property (@(posedge clk) disable iff (!rst_n)
    (sch_en |-> |(req&gnt))
) else begin
    $error("reg %b by gnt %b is null", req, gnt);
    $stop;
end

a_gnt_onehot: assert property (@(posedge clk) disable iff (!rst_n)
    (sch_en |-> $onehot(gnt))
) else begin
    $error("gnt %b is not onehot", gnt);
    $stop;
end

a_gnt_hld: assert property (@(posedge clk) disable iff (!rst_n)
    (sch_en |-> ##1 (gnt_hld==$past(gnt,1)))
) else begin
    $error("gnt_hld %b not equal last gnt %b", gnt_hld, $past(gnt,1));
    $stop;
end

genvar v0;
generate for (v0=0; v0<PORT_NUM; v0=v0+1) begin:V_PORT

    a_weight_under_flow: assert property (@(posedge clk) disable iff (!rst_n)
        ((~(sch_en&(~req_cnt_mask_or)))&gnt_1d[v0]) |-> ##1 (cnt_weight[v0]!={WEIGHT_BW{1'b1}})
    ) else begin
        $error("cnt_weight[%0d] is under flow.", v0);
        $stop;
    end

end endgenerate

c_update_weight_b2b: cover property (@(posedge clk) disable iff (!rst_n)
    sch_en&(~req_cnt_mask_or)&$past(sch_en, 1)&(~$past(req_cnt_mask_or, 1))
);

`endif

// synopsys translate_on
`endif

endmodule
